#include "network.hh"
#include "node.hh"
#include "connection.hh"
#include <algorithm>
#include <utility>
using namespace std;

namespace {
    constexpr int OFFSET = 0, SIZE = 1, TYPE = 2, BIAS = 3;
}

namespace ann {

    struct Network::impl_t {
        //state of the network (output of neurons)
        vector<float> state;

        //weights in-order
        vector<float> weights;
        vector<size_t> inputs;

        //offset of weights, number of weights, activation_fun bias
        vector<tuple<size_t, size_t, int, float>> controls; 
    };

    Network::Network (size_t inputs, size_t bias, size_t outputs, 
                      activation_function_t activation,
                      NodeCollection const& nodes)
    : impl(new impl_t{}), activation(activation),
      inputs(inputs), bias(bias), outputs(outputs)
    {
        create_network(nodes);
    }

    Network::~Network() { delete impl; }

    void
    Network::create_network (NodeCollection const& nodes) {
        impl->state.reserve(nodes.size());
        impl->controls.reserve(nodes.size());

        size_t current_offset = 0;
        for (auto const& n: nodes.nodes) {        
            Node const* neuron = n.first;
            const size_t n_connections = neuron->count_connections();
                
            for (size_t i = 0; i < n_connections; ++i) {
                Connection const* connection = neuron->get_input(i);
                const size_t other_id = nodes.id_of(connection->get_source());
                const float weight = connection->get_weight();
          
                impl->weights.push_back(weight);
                impl->inputs.push_back(other_id);
            }
           
            impl->state.push_back(0);
            impl->controls.push_back(make_tuple(current_offset, n_connections, 
                                                neuron->get_type(),
                                                neuron->get_bias()));
            current_offset += n_connections;
        }
    }

    
    void
    Network::step () {
        for (size_t neuron = 0; neuron < impl->controls.size(); ++neuron) {
            auto const& ctrl = impl->controls[neuron];
            size_t const offset = std::get<OFFSET>(ctrl);
            size_t const size = std::get<SIZE>(ctrl);
            const int type = std::get<TYPE>(ctrl);
            float const bias = std::get<BIAS>(ctrl);

            if (neuron >= inputs+bias) {
                float input = 0;
                for (size_t s = offset; s < offset+size; ++s) {
                    input += impl->state[impl->inputs[s]] * impl->weights[s];
                }
                
                float out = activation(type, input, bias);
                if (out > 1) out = 1;
                else if (out < -1) out = -1;
                else if (isnan(out)) out = 0;            
                impl->state[neuron] = out;
            } 
        }
    }

    void
    Network::set_input (std::initializer_list<float> const& init) {
        assert(init.size() == inputs);

        int i = 0;
        for (float const& f: init) {
            impl->state[i] = f;
            i++;
        }

        for (size_t b = inputs; b < inputs+bias; ++b) {
            impl->state[b] = 1; //bias
        }
    }

    void
    Network::reset() {
        fill(impl->state.begin(), impl->state.end(), 0.0f);
    }

    float
    Network::get (size_t output) const {
        const size_t index = impl->state.size() - outputs + output;
        return impl->state[index];
    }

}














